package org.jvm.instruction.references;

import org.jvm.instruction.base.NoOperandsInstruction;
import org.jvm.rtda.Object;
import org.jvm.rtda.StackTraceElement;
import org.jvm.rtda.thread.Thread;
import org.jvm.rtda.thread.*;
import org.jvm.util.JvmUtil;

import java.util.Optional;

/**
 * 异常抛出
 *
 * @author 海燕
 * @date 2023/2/6
 */
public class ATHROW extends NoOperandsInstruction {


    /**
     * 指令操作数含义为类常量池索引
     *
     * @param frame
     */
    @Override
    public void execute(Frame frame) {
        throwException(frame.getOperandStack().popRef(), frame.getThread());
    }

    /**
     * 抛出指定类型的异常
     *
     * @param throwable
     */
    public static void throwException(Object throwable, Thread thread) {
        if (throwable == null) {
            thread.throwNullPointerException();
            return;
        }
        //如果没有找到能处理异常的栈帧，此时线程的方法栈已空。JVM开始打印异常信息
        if (!findAndGotoExceptionHandler(thread, throwable)) {
            handleUncaughtException(thread, throwable);
        }
    }

    /**
     * 处理未被处理的异常，JVM负责打印异常信息
     *
     * @param thread
     * @param exceptionRef
     */
    private static void handleUncaughtException(Thread thread, Object exceptionRef) {
        //清空线程栈帧，线程停止
        thread.clearStack();
        //打印错误信息
        Object detailMessageRef = exceptionRef.getRefVar("detailMessage", "Ljava/lang/String;");
        String detailMessage = Optional.ofNullable(JvmUtil.javaStringToJvmString(detailMessageRef)).orElse("");
        String message = exceptionRef.getKlass().getJavaName().concat(":").concat(detailMessage);
        System.out.println("Exception in thread \"" + thread.getName() + "\" " + message);
        //打印异常栈
        StackTraceElement[] stackTraceElements = (StackTraceElement[]) exceptionRef.getExtra();
        for (int i = 0; i < stackTraceElements.length; i++) {
            System.out.println("    " + stackTraceElements[i]);
        }
    }

    /**
     * 挨个栈帧向下查找，直到找到能处理异常的栈帧，并将异常交给其处理
     *
     * @param thread
     * @param exceptionRef
     * @return true-最终找到了 false-最终没找到
     */
    private static boolean findAndGotoExceptionHandler(Thread thread, Object exceptionRef) {

        for (; !thread.getStack().empty(); thread.getStack().pop()) {
            Frame topFrame = thread.getStack().top();
            /**
             * 这里程序计数器pc-1的目的应该是这样的
             * nextPc是下一条未执行指令的起始位置，如果try块中的在执行到最后一条指令时抛异常
             * 此时程序计数器已经指向catch块中的指令，已经超出了try块能处理的字节码范围。
             * 在这里-1后再判断pc的位置是否再try块内
             */
            int exceptionPc = topFrame.getNextPC() - 1;
            int exceptionHandlerPc = topFrame.getMethod().findExceptionHandler(exceptionRef.getKlass(), exceptionPc, thread);
            if (exceptionHandlerPc == -1) {
                continue;
            }
            //将栈帧的程序计数器指向catch块
            topFrame.setNextPC(exceptionHandlerPc);
            //清空栈帧操作数栈，并将Exception实例压栈
            topFrame.getOperandStack().clear();
            topFrame.getOperandStack().pushRef(exceptionRef);
            return true;
        }
        return false;
    }
}
